/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/delay.h>

#include <alga/alga.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/dce6/dce6_dev.h>

#include "dce6.h"
#include "i2c.h"
#include "dpcd.h"
#include "sysfs.h"
#include "regs.h"

#define RR32(of) dce->ddev.rr32(dce->ddev.dev, (of))
#define WR32(v, of) dce->ddev.wr32(dce->ddev.dev, (v), (of))
static void off(struct dce6 *dce, u8 hpd)
{
	WR32(0, regs_hpd_ctl[hpd]);
}

static void on(struct dce6 *dce, u8 hpd)
{
	u32 hpd_ctl;

	/*
 	 * This is hardcoded for displayport with the 2 ms threshold in mind.
 	 * In theory, the irq is not supposed to be raised until the hpd logic
 	 * knows if it is a disconnection or a dp sink irq.
	 */
	hpd_ctl = set(HC_CONN_TIMER, 2500) | set(HC_RX_INT_TIMER, 250) | HC_ENA; 
	WR32(hpd_ctl, regs_hpd_ctl[hpd]);
}

bool hpd_sense(struct dce6 *dce, u8 hpd)
{
	return ((RR32(regs_hpd_int_status[hpd]) & HIS_STATUS_SENSE) != 0);
}


void hpd_polarity_rearm(struct dce6 *dce, u8 hpd, u8 connected)
{
	u32 hpd_int_ctl;

	hpd_int_ctl = RR32(regs_hpd_int_ctl[hpd]);
	if (connected)
		hpd_int_ctl &= ~HIC_INT_POLARITY;
	else
		hpd_int_ctl |= HIC_INT_POLARITY;
	WR32(hpd_int_ctl, regs_hpd_int_ctl[hpd]);
}

void hpds_polarity_refresh(struct dce6 *dce)
{
	u8 i;

	/* keep polarity and reset even if not used */
	for (i = 0; i < HPDS_N; ++i) {
		u32 hpd_int_ctl;

		hpd_int_ctl = RR32(regs_hpd_int_ctl[i]) & HIC_INT_POLARITY;
		WR32(hpd_int_ctl, regs_hpd_int_ctl[i]);
	}
}

void hpds_off(struct dce6 *dce)
{
	u8 i;

	for (i = 0; i < HPDS_N; ++i)
		off(dce, i);
}

void hpds_init(struct dce6 *dce)
{
	u8 i;

	for (i = 0; i < HPDS_N; ++i) {
		if ((dce->hpds_used & BIT(i)) == 0) {
			off(dce, i);
			dev_info(dce->ddev.dev, "dce6:hpd%u off\n", i);
			continue;
		}

		on(dce, i);
		dev_info(dce->ddev.dev, "dce6:hpd%u on\n", i);
	}
}

static long dp_toggle_connected(struct dce6 *dce, u8 i)
{
	long r;

	r = dp_i2c_adapter_init(dce, i);
	if (r == -DCE6_ERR) {
		dev_warn(dce->ddev.dev,
	"dce6:unable to create i2c adapter for newly connected dp%u\n", i);
		goto exit;
	}

	/* don't be too hasty, specs give us usually 100 ms to get dcpcd info */
	mdelay(60);

	/* dp specs: must query dcpd info before 100 ms */
	r = dpcd_info(dce, i); 
	if (r == -DCE6_ERR) {
		dev_warn(dce->ddev.dev,
		"dce6:unable to query dcpd info for newly connected dp%u\n", i);
		goto del_i2c_adapter;
	}

	/* get the edid, failing to get it is not fatal */
	r = alga_i2c_edid_fetch(dce->ddev.dev, &dce->dps[i].i2c_adapter,
							&dce->dps[i].edid);
	if (r == -ALGA_ERR) {
		dev_warn(dce->ddev.dev, "dce6:unable to fetch edid for dp%u\n",
									i);
		r = 0;
	}
	dce->dps[i].edid_sz = r;

	/* sysfs */
	r = sysfs_add(dce, i);
	if (r == -DCE6_ERR)
		goto free_edid;
	r = 0;
	goto exit;

free_edid:
	if (dce->dps[i].edid) {
		kfree(dce->dps[i].edid);
		dce->dps[i].edid = NULL;
	}

del_i2c_adapter:
	i2c_del_adapter(&dce->dps[i].i2c_adapter);

exit:
	return r;
}

/* must be called with the lock held */
static long dp_toggle(struct dce6 *dce, u8 i)
{
	long r;
	dce->dps[i].connected = !dce->dps[i].connected;	

	r = 0;
	if(dce->dps[i].connected) {
		dev_info(dce->ddev.dev, "dce6:dp%u was connected\n", i);
		r = dp_toggle_connected(dce, i);
		/* restore disconnected state, and tag for error */
		if (r == -DCE6_ERR) {
			dce->dps[i].connected = 0;
			dce->dps[i].dpcd_info[0] = 0; /* error */
		}
	} else {
		dev_info(dce->ddev.dev, "dce6:dp%u was disconnected\n", i);
		dp_dpm_off(dce, i);
		i2c_del_adapter(&dce->dps[i].i2c_adapter);
		if (dce->dps[i].edid != NULL) {
			kfree(dce->dps[i].edid);
			dce->dps[i].edid = NULL;
		}
		sysfs_remove(dce, i);
	}
	atb_dp_state(dce->ddev.atb, dce->dps[i].atb_dfp, dce->dps[i].connected);
	return r;
}

static void dp_sink_irq(u8 i)
{
	printk(KERN_INFO "DP%u SINK IRQ RECEIVED\n", i);
	/* TODO: first re-read its dpcd info... */
}

/* management of a spurious hotplug */
static void spurious(struct dce6 *dce, u8 i, u8 sense)
{
	if (sense == 0) {
		dev_warn(dce->ddev.dev, "dce6:dp%u spurious disconnexion\n", i);
		return;
	}

	/*
	 * we hotplugged an already plugged display:
	 *   dp specs-->this is a sink interrupt
	 */
	dp_sink_irq(i);
	return;
}

long hpd_irq(struct dce6 *dce, u8 hpd)
{
	long r;
	u8 i;

	if ((dce->hpds_used & BIT(hpd)) == 0) {
		dev_warn(dce->ddev.dev, "dce6:irq on unused hpd%u\n, hpd", hpd);
		return 0;
	}

	r = 0;
	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if (dce->dps[i].hpd == hpd) {
			u8 sense;

			sense = hpd_sense(dce, hpd);
			hpd_polarity_rearm(dce, hpd, sense);

			lock(dce);
			if (dce->dps[i].connected != sense) {
				if (dce->irq.hpd_dev_registration_lock == 1
								&& sense == 1) {
					dev_warn(dce->ddev.dev, "dce6:attempt to register a display device on dp%u while suspending\n",
									i);
				} else {
					r = dp_toggle(dce, i);
				}
			} else {
				spurious(dce, i, sense);
			}
			unlock(dce);
			break;
		}
	}
	return r;
}

void dce6_hpds_intr_ena(struct dce6 *dce)
{
	u32 hpds[HPDS_N];
	u8 i;

	for (i = 0; i < HPDS_N; ++i) {
		if (dce->hpds_used & BIT(i))
			hpds[i] = RR32(regs_hpd_int_ctl[i]) | HIC_INT_ENA;
		else
			hpds[i] = 0;

	}
	for (i = 0; i < HPDS_N; ++i)
		WR32(hpds[i], regs_hpd_int_ctl[i]);
}
EXPORT_SYMBOL_GPL(dce6_hpds_intr_ena);

void dce6_hpd_irq(struct dce6 *dce, u8 hpd)
{
	/* we just flag the hpd and we will do the work in the irq thread */
	spin_lock(&dce->irq.hpds_lock);
	dce->irq.hpds |= BIT(hpd);
	spin_unlock(&dce->irq.hpds_lock);
}
EXPORT_SYMBOL_GPL(dce6_hpd_irq);
